﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Linq;

namespace Dispatcher.Extensions
{
    /// <summary>
    /// Object context extensions.
    /// </summary>
    public static class ObjectContextExtensions
    {
        /// <summary>
        /// Gets the object set.
        /// </summary>
        /// <param name="objectContext">The object context.</param>
        /// <param name="type">The type.</param>
        /// <returns>
        /// Object set.
        /// </returns>
        public static IQueryable GetObjectSet(this ObjectContext objectContext, Type type)
        {
            var objectSet = GetObjectSets(objectContext).FirstOrDefault(s => s.ElementType == type);
            return objectSet;
        }

        /// <summary>
        /// Gets the object set.
        /// </summary>
        /// <param name="objectContext">The object context.</param>
        /// <param name="objectSetName">Name of the object set.</param>
        /// <returns>Object set.</returns>
        public static IQueryable GetObjectSet(this ObjectContext objectContext, string objectSetName)
        {
            var properties = objectContext.GetType().GetProperties();
            var objectSetProperty = properties.FirstOrDefault(p => p.Name == objectSetName);
            var objectSet = objectSetProperty.GetValue(objectContext) as IQueryable;
            return objectSet;
        }

        /// <summary>
        /// Gets the object set.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <param name="objectContext">The object context.</param>
        /// <returns>Object set.</returns>
        public static ObjectSet<TEntity> GetObjectSet<TEntity>(this ObjectContext objectContext) where TEntity : EntityObject
        {
            var objectSet = objectContext.GetObjectSets().FirstOrDefault(s => s.ElementType == typeof(TEntity));
            return objectSet as ObjectSet<TEntity>;
        }

        /// <summary>
        /// Gets the name of the object set by entity type.
        /// </summary>
        /// <param name="objectContext">The object context.</param>
        /// <param name="entityTypeName">Name of the entity type.</param>
        /// <returns>Object set.</returns>
        public static IQueryable GetObjectSetByEntityTypeName(this ObjectContext objectContext, string entityTypeName)
        {
            var objectSet = objectContext.GetObjectSets().FirstOrDefault(s => s.ElementType.Name == entityTypeName);
            return objectSet;
        }

        /// <summary>
        /// Gets the name of the property.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <param name="objectContext">The object context.</param>
        /// <param name="objectSet">The object set.</param>
        /// <returns>
        /// Object set property name.
        /// </returns>
        public static string GetPropertyName<TEntity>(this ObjectContext objectContext, ObjectSet<TEntity> objectSet) where TEntity : EntityObject
        {
            var properties = objectContext.GetType().GetProperties();
            var objectSetProperty = properties.First(p => p.GetValue(objectContext) == objectSet);
            return objectSetProperty.Name;
        }

        /// <summary>
        /// Gets the name of the property.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <param name="objectSet">The object set.</param>
        /// <returns>Object set property name.</returns>
        public static string GetPropertyName<TEntity>(this ObjectSet<TEntity> objectSet) where TEntity : EntityObject
        {
            var objectContext = objectSet.Context;
            var properties = objectContext.GetType().GetProperties();
            var objectSetProperty = properties.First(p => p.GetValue(objectContext) == objectSet);
            return objectSetProperty.Name;
        }

        /// <summary>
        /// Gets the object sets.
        /// </summary>
        /// <param name="objectContext">The object context.</param>
        /// <returns>Object sets.</returns>
        public static IEnumerable<IQueryable> GetObjectSets(this ObjectContext objectContext)
        {
            var properties = objectContext.GetType().GetProperties();
            var objectSets = properties.Select(p => p.GetValue(objectContext)).OfType<IQueryable>().ToList();
            return objectSets;
        }

        /// <summary>
        /// Determines whether the specified object context has changes.
        /// </summary>
        /// <param name="objectContext">The object context.</param>
        /// <returns>
        ///   <c>true</c> if the specified object context has changes; otherwise, <c>false</c>.
        /// </returns>
        public static bool HasChanges(this ObjectContext objectContext)
        {
            objectContext.DetectChanges();
            var addedItems = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added);
            var modifiedItems = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
            var deletedItems = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted);
            var hasChanges = addedItems.Any() || modifiedItems.Any() || deletedItems.Any();
            return hasChanges;
        }

        /// <summary>
        /// Undoes the specified object context.
        /// </summary>
        /// <param name="objectContext">The object context.</param>
        public static void Undo(this ObjectContext objectContext)
        {
            objectContext.DetectChanges();
            var addedItems = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added);
            var modifiedItems = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
            var deletedItems = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted);

            //objectContext.Refresh(RefreshMode.StoreWins, modifiedItems);
            //foreach (var objectStateEntry in modifiedItems)
            {
                //objectContext.Refresh(RefreshMode.StoreWins, objectStateEntry);
            }

            foreach (var objectStateEntry in addedItems)
            {
                objectStateEntry.ChangeState(EntityState.Detached);
            }

            foreach (var objectStateEntry in deletedItems)
            {
                //objectStateEntry.ApplyOriginalValues(objectStateEntry.OriginalValues);
            }
        }
    }
}
